package cbr

import (
	"fmt"
	"log"
	"sync"
	"time"
)

// NetworkConfig holds network configuration for CBR
type NetworkConfig struct {
	BaseIP   string
	BasePort int
	IPList   []string
	PortList []string
}

// EbbFlowCBRConfig holds configuration for EbbFlow CBR integration
type EbbFlowCBRConfig struct {
	NodeID                  int
	TotalNodes              int
	FaultyNodes             int
	ReconfigurationInterval int // Execute CBR every N epochs
	NetworkConfig           NetworkConfig
	EnableMetrics           bool
}

// CBRResult holds the result of CBR execution
type CBRResult struct {
	Epoch          int
	Success        bool
	Duration       time.Duration
	NewMembership  []string
	ProactivizeKey []byte
	Error          error
}

// CBRMetrics tracks CBR performance metrics
type CBRMetrics struct {
	TotalExecutions    int
	SuccessfulExecutions int
	FailedExecutions   int
	TotalDuration      time.Duration
	AverageDuration    time.Duration
	LastExecution      time.Time
	mu                 sync.RWMutex
}

// EbbFlowCBRIntegration integrates CBR protocol with EbbFlow
type EbbFlowCBRIntegration struct {
	config     EbbFlowCBRConfig
	node       *CBRNode
	metrics    *CBRMetrics
	proofChain *ProofChain
	logger     *log.Logger
	mu         sync.RWMutex
}

// NewEbbFlowCBRIntegration creates a new EbbFlow CBR integration
func NewEbbFlowCBRIntegration(config EbbFlowCBRConfig, logger *log.Logger) *EbbFlowCBRIntegration {
	if logger == nil {
		logger = log.Default()
	}
	
	node := NewCBRNode(config.NodeID, config.TotalNodes, config.FaultyNodes, logger)
	
	return &EbbFlowCBRIntegration{
		config:     config,
		node:       node,
		metrics:    &CBRMetrics{},
		proofChain: NewProofChain(),
		logger:     logger,
	}
}

// Execute executes the CBR protocol
func (e *EbbFlowCBRIntegration) Execute() error {
	_, err := e.ExecuteReconfiguration(0, e.generateDefaultMembership())
	return err
}

// ExecuteReconfiguration executes CBR reconfiguration for a specific epoch
func (e *EbbFlowCBRIntegration) ExecuteReconfiguration(epoch int, newMembership []string) (*CBRResult, error) {
	startTime := time.Now()
	
	e.logger.Printf("[EbbFlow CBR] Starting reconfiguration for epoch %d", epoch)
	
	// Execute CBR protocol
	err := e.node.Execute(epoch, newMembership)
	
	duration := time.Since(startTime)
	
	result := &CBRResult{
		Epoch:         epoch,
		Success:       err == nil,
		Duration:      duration,
		NewMembership: newMembership,
		Error:         err,
	}
	
	if err == nil {
		result.ProactivizeKey = e.node.GetProactivizedKey()
		
		// Add proof entry to proof chain
		memberIDs := e.membershipToIDs(newMembership)
		proofEntry := &ProofEntry{
			Epoch:   epoch,
			N:       e.config.TotalNodes,
			F:       e.config.FaultyNodes,
			L:       len(newMembership),
			Members: memberIDs,
		}
		
		if err := e.proofChain.AddEntry(proofEntry); err != nil {
			e.logger.Printf("[EbbFlow CBR] Failed to add proof entry for epoch %d: %v", epoch, err)
		} else {
			e.logger.Printf("[EbbFlow CBR] Proof entry added for epoch %d", epoch)
		}
	}
	
	// Update metrics
	if e.config.EnableMetrics {
		e.updateMetrics(result)
	}
	
	if err != nil {
		e.logger.Printf("[EbbFlow CBR] Reconfiguration failed for epoch %d: %v", epoch, err)
		return result, err
	}
	
	e.logger.Printf("[EbbFlow CBR] Reconfiguration completed for epoch %d in %v", epoch, duration)
	return result, nil
}

// ShouldExecuteCBR determines if CBR should be executed at the given epoch
func (e *EbbFlowCBRIntegration) ShouldExecuteCBR(epoch int) bool {
	if e.config.ReconfigurationInterval <= 0 {
		return false
	}
	return epoch > 0 && epoch%e.config.ReconfigurationInterval == 0
}

// GetCBRMetrics returns CBR performance metrics
func (e *EbbFlowCBRIntegration) GetCBRMetrics() *CBRMetrics {
	e.metrics.mu.RLock()
	defer e.metrics.mu.RUnlock()
	
	// Return a copy
	return &CBRMetrics{
		TotalExecutions:      e.metrics.TotalExecutions,
		SuccessfulExecutions: e.metrics.SuccessfulExecutions,
		FailedExecutions:     e.metrics.FailedExecutions,
		TotalDuration:        e.metrics.TotalDuration,
		AverageDuration:      e.metrics.AverageDuration,
		LastExecution:        e.metrics.LastExecution,
	}
}

// GetCurrentPhase returns the current CBR phase
func (e *EbbFlowCBRIntegration) GetCurrentPhase() string {
	return e.node.GetCurrentPhase().String()
}

// updateMetrics updates performance metrics
func (e *EbbFlowCBRIntegration) updateMetrics(result *CBRResult) {
	e.metrics.mu.Lock()
	defer e.metrics.mu.Unlock()
	
	e.metrics.TotalExecutions++
	if result.Success {
		e.metrics.SuccessfulExecutions++
	} else {
		e.metrics.FailedExecutions++
	}
	
	e.metrics.TotalDuration += result.Duration
	e.metrics.AverageDuration = e.metrics.TotalDuration / time.Duration(e.metrics.TotalExecutions)
	e.metrics.LastExecution = time.Now()
}

// generateDefaultMembership generates default membership list
func (e *EbbFlowCBRIntegration) generateDefaultMembership() []string {
	membership := make([]string, e.config.TotalNodes)
	
	for i := 0; i < e.config.TotalNodes; i++ {
		var ip, port string
		
		if i < len(e.config.NetworkConfig.IPList) {
			ip = e.config.NetworkConfig.IPList[i]
		} else {
			ip = e.config.NetworkConfig.BaseIP
		}
		
		if i < len(e.config.NetworkConfig.PortList) {
			port = e.config.NetworkConfig.PortList[i]
		} else {
			port = fmt.Sprintf("%d", e.config.NetworkConfig.BasePort+i)
		}
		
		membership[i] = fmt.Sprintf("%s:%s", ip, port)
	}
	
	return membership
}

// PrintMetrics prints current metrics
func (e *EbbFlowCBRIntegration) PrintMetrics() {
	metrics := e.GetCBRMetrics()
	
	e.logger.Printf("=== CBR Metrics ===")
	e.logger.Printf("Total Executions: %d", metrics.TotalExecutions)
	e.logger.Printf("Successful: %d", metrics.SuccessfulExecutions)
	e.logger.Printf("Failed: %d", metrics.FailedExecutions)
	e.logger.Printf("Average Duration: %v", metrics.AverageDuration)
	e.logger.Printf("Last Execution: %v", metrics.LastExecution)
}

// GetProofChain returns the proof chain
func (e *EbbFlowCBRIntegration) GetProofChain() *ProofChain {
	return e.proofChain
}

// GetProofEntry retrieves a proof entry by epoch
func (e *EbbFlowCBRIntegration) GetProofEntry(epoch int) *ProofEntry {
	return e.proofChain.GetEntry(epoch)
}

// membershipToIDs converts membership strings to node IDs
// Since membership is []string, we generate sequential IDs based on the membership size
func (e *EbbFlowCBRIntegration) membershipToIDs(membership []string) []int {
	ids := make([]int, len(membership))
	for i := range membership {
		ids[i] = i
	}
	return ids
}

